/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_bsp.cpp - BSP format loading
//


#include "r_local.h"


static texture_t *			r_textureArray[MAX_TEXTURES];


/*
 ==================
 R_LoadPlanes
 ==================
*/
static void R_LoadPlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	glqPlane	*out;
	glqVec3		normal;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numPlanes = lump->length / sizeof(bspPlane_t);
	if (rg.world->numPlanes < 1)
		Com_Error(false, "R_LoadMap: no planes (%s)", rg.world->name);
	else if (rg.world->numPlanes > BSP_MAX_PLANES)
		Com_Error(false, "R_LoadMap: too many planes (%s)", rg.world->name);

	rg.world->planes = out = (glqPlane *)Mem_Alloc(rg.world->numPlanes * sizeof(glqPlane), TAG_RENDERER);
	rg.world->size += rg.world->numPlanes * sizeof(glqPlane);

	for (i = 0; i < rg.world->numPlanes; i++, in++, out++){
		normal[0] = LittleFloat(in->normal[0]);
		normal[1] = LittleFloat(in->normal[1]);
		normal[2] = LittleFloat(in->normal[2]);

		out->Set(normal, LittleFloat(in->dist));
	}
}

/*
 ==================
 
 ==================
*/
static void R_SetupAnimations (){

	texture_t	*texture;
	int			i;

	for (i = 0; i < rg.world->numTextures; i++){
		texture = r_textureArray[i];

		if (texture->name[0] != '+')
			continue;
	}
}

/*
 ==================
 R_LoadTextures

 NOTE: we do not check for "funny lump size" here because Id Software somehow
 messed it up
 ==================
*/
static void R_LoadTextures (const byte *data, const bspLump_t *lump){

	bspMipTexture_t	*in;
	texture_t		*out;
	mipTexture_t	*mt;
	char			name[MAX_PATH_LENGTH];
	int				i, j;

	in = (bspMipTexture_t *)(data + lump->offset);

	rg.world->numTextures = LittleLong(in->numMipTextures);
	if (rg.world->numTextures < 1)
		Com_Error(false, "R_LoadMap: no textures (%s)", rg.world->name);
	else if (rg.world->numTextures > BSP_MAX_TEXTURES)
		Com_Error(false, "R_LoadMap: too many textures (%s)", rg.world->name);

	rg.world->textures = out = (texture_t *)Mem_Alloc(rg.world->numTextures * sizeof(texture_t), TAG_RENDERER);
	rg.world->size += rg.world->numTextures * sizeof(texture_t);

	for (i = 0; i < rg.world->numTextures; i++){
		in->dataOfs[i] = LittleLong(in->dataOfs[i]);
		if (in->dataOfs[i] == -1)
			continue;

		mt = (mipTexture_t *)((byte *)in + in->dataOfs[i]);

		mt->width = LittleLong(mt->width);
		mt->height = LittleLong(mt->height);

		for (j = 0; j < MIP_LEVELS; j++)
			mt->offsets[j] = LittleLong(mt->offsets[j]);

		if ((mt->width & 15) || (mt->height & 15))
			Com_Error(false, "R_LoadMap: texture %s is not 16 aligned", mt->name);

		// Load the texture
		Str_SPrintf(name, sizeof(name), "textures/%s", mt->name);

		// FIXME: rename ALL textures with these characters: '*' '#' ('+' names are used for animation)
		if (!Str_ICompare(name, "textures/*lava1")){
			Str_Copy(name, "textures/lava1", sizeof(name));
		}

		// TODO: for skies, we need to load a R_FindLumpCubeTexture

		r_textureArray[i] = R_FindLumpTexture(name, 0, TF_DEFAULT, TW_REPEAT, mt->width, mt->height, ((byte *)(mt + 1)) + mt->offsets[0]);
	}

	// Set up animation sequences
	R_SetupAnimations();
}

/*
 ==================
 R_LoadVertices
 ==================
*/
static void R_LoadVertices (const byte *data, const bspLump_t *lump){

	bspVertex_t	*in;
	glVertex_t	*out;
	int			i;

	in = (bspVertex_t *)(data + lump->offset);
	if (lump->length % sizeof(bspVertex_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numVertices = lump->length / sizeof(bspVertex_t);
	if (rg.world->numVertices < 0)
		Com_Error(false, "R_LoadMap: no vertices (%s)", rg.world->name);
	else if (rg.world->numVertices > BSP_MAX_VERTICES)
		Com_Error(false, "R_LoadMap: too many vertices (%s)", rg.world->name);

	rg.world->vertices = out = (glVertex_t *)Mem_Alloc(rg.world->numVertices * sizeof(glVertex_t), TAG_RENDERER);
	rg.world->size += rg.world->numVertices * sizeof(glVertex_t);

	for (i = 0; i < rg.world->numVertices; i++, in++, out++){
		out->xyz[0] = LittleFloat(in->point[0]);
		out->xyz[1] = LittleFloat(in->point[1]);
		out->xyz[2] = LittleFloat(in->point[2]);
	}
}

/*
 ==================
 R_LoadPVS
 ==================
*/
static void R_LoadPVS (const byte *data, const bspLump_t *lump){

	rg.world->noPVS = (byte *)Mem_Alloc(255, TAG_RENDERER);
	rg.world->size += 255;

	Mem_Fill(rg.world->noPVS, 0xFF, 255);

	if (!lump->length){
		rg.world->pvs = NULL;
		return;
	}

	if (lump->length > BSP_MAX_PVS)
		Com_Error(false, "R_LoadMap: too large PVS (%s)", rg.world->name);

	rg.world->pvs = (byte *)Mem_Alloc(lump->length, TAG_RENDERER);
	rg.world->size += lump->length;

	Mem_Copy(rg.world->pvs, data + lump->offset, lump->length);
}

/*
 ==================
 R_LoadEdges
 ==================
*/
static void R_LoadEdges (const byte *data, const bspLump_t *lump){

	bspEdge_t	*in;
	edge_t		*out;
	int			i;

	in = (bspEdge_t *)(data + lump->offset);
	if (lump->length % sizeof(bspEdge_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numEdges = lump->length / sizeof(bspEdge_t);
	if (rg.world->numEdges < 0)
		Com_Error(false, "R_LoadMap: no edges (%s)", rg.world->name);
	else if (rg.world->numEdges > BSP_MAX_EDGES)
		Com_Error(false, "R_LoadMap: too many edges (%s)", rg.world->name);

	rg.world->edges = out = (edge_t *)Mem_Alloc(rg.world->numEdges * sizeof(edge_t), TAG_RENDERER);
	rg.world->size += rg.world->numEdges * sizeof(edge_t);

	for (i = 0; i < rg.world->numEdges; i++, in++, out++){
		out->v[0] = (ushort)LittleShort(in->v[0]);
		out->v[1] = (ushort)LittleShort(in->v[1]);
	}
}

/*
 ==================
 R_LoadSurfEdges
 ==================
*/
static void R_LoadSurfEdges (const byte *data, const bspLump_t *lump){

	int		*in;
	int		*out;
	int		i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numSurfEdges = lump->length / sizeof(int);
	if (rg.world->numSurfEdges < 0)
		Com_Error(false, "R_LoadMap: no surf edges (%s)", rg.world->name);
	else if (rg.world->numSurfEdges > BSP_MAX_SURFEDGES)
		Com_Error(false, "R_LoadMap: too many surf edges (%s)", rg.world->name);

	rg.world->surfEdges = out = (int *)Mem_Alloc(rg.world->numSurfEdges * sizeof(int), TAG_RENDERER);
	rg.world->size += rg.world->numSurfEdges * sizeof(int);

	for (i = 0; i < rg.world->numSurfEdges; i++)
		out[i] = LittleLong(in[i]);
}

/*
 ==================
 R_GetTexInfoSize
 ==================
*/
static void R_GetTexInfoSize (texInfo_t *texInfo, int width, int height){

	material_t	*material = texInfo->material;
	stage_t		*stage;
	char		name[MAX_PATH_LENGTH];
	int			i;

	// Look for a .tga texture first, if found, then force the dimensions so that
	// re-textures can work
	Str_SPrintf(name, sizeof(name), "%s.tga", material->name);
	if (FS_FileExists(name)){
		texInfo->width = width;
		texInfo->height = height;
		return;
	}

	// Look for the first texture stage in the material
	for (i = 0, stage = material->stages; i < material->numStages; i++, stage++){
		if (stage->textureStage.texture->flags & TF_INTERNAL)
			continue;

		texInfo->width = stage->textureStage.texture->width;
		texInfo->height = stage->textureStage.texture->height;
		return;
	}

	// Default to 64x64
	texInfo->width = 64;
	texInfo->height = 64;
}

/*
 ==================
 R_CheckTexInfoClamp

 FIXME: only allow 2D textures?, also do not
 allow a material using shaders
 ==================
*/
static void R_CheckTexInfoClamp (texInfo_t *texInfo){

	material_t	*material = texInfo->material;
	stage_t		*stage;
	int			i;

	// If at least one stage uses any form of texture clamping, then all
	// surfaces using this material will need their texture coordinates
	// to be clamped to the 0.0 - 1.0 range for things to work properly
	for (i = 0, stage = material->stages; i < material->numStages; i++, stage++){
		if (stage->textureStage.texture->type != TT_2D)
			continue;

		if (stage->textureStage.texture->wrap != TW_REPEAT){
			texInfo->clamp = true;
			return;
		}
	}

	// No need to clamp texture coordinates
	texInfo->clamp = false;
}

/*
 ==================
 R_LoadTexInfo
 ==================
*/
static void R_LoadTexInfo (const byte *data, const bspLump_t *lump){

	bspTexInfo_t	*in;
	texInfo_t		*out;
	int				index;
	int				i, j;

	in = (bspTexInfo_t *)(data + lump->offset);
	if (lump->length % sizeof(bspTexInfo_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numTexInfo = lump->length / sizeof(bspTexInfo_t);
	if (rg.world->numTexInfo < 0)
		Com_Error(false, "R_LoadMap: no tex info (%s)", rg.world->name);
	else if (rg.world->numTexInfo > BSP_MAX_TEXINFO)
		Com_Error(false, "R_LoadMap: too many tex info (%s)", rg.world->name);

	rg.world->texInfo = out = (texInfo_t *)Mem_Alloc(rg.world->numTexInfo * sizeof(texInfo_t), TAG_RENDERER);
	rg.world->size += rg.world->numTexInfo * sizeof(texInfo_t);

	for (i = 0; i < rg.world->numTexInfo; i++, in++, out++){
		out->flags = LittleLong(in->flags);

		for (j = 0; j < 4; j++){
			out->vecs[0][j] = LittleFloat(in->vecs[0][j]);
			out->vecs[1][j] = LittleFloat(in->vecs[1][j]);
		}

		index = LittleLong(in->miptex);

		// Special case for sky surfaces
		if (!Str_ICompare(r_textureArray[index]->name, "textures/sky4")){
			out->material = R_FindMaterial(r_textureArray[index]->name, MT_GENERIC, SURFACEPARM_SKY);

			out->width = 64;
			out->height = 64;
			out->clamp = false;

			continue;
		}

		// Load the material
		out->material = R_FindMaterial(r_textureArray[index]->name, MT_GENERIC, SURFACEPARM_LIGHTING);

		// Find texture dimensions
		R_GetTexInfoSize(out, r_textureArray[index]->lumpWidth, r_textureArray[index]->lumpHeight);

		// Check if surfaces will need clamping
		R_CheckTexInfoClamp(out);
	}
}

/*
 ==================
 R_CalcSurfaceBounds

 Fills in surface bounds
 ==================
*/
static void R_CalcSurfaceBounds (surface_t *surface){

	glVertex_t	*v;
	int			i, e;

	surface->bounds.Clear();

	for (i = 0; i < surface->numEdges; i++){
		e = rg.world->surfEdges[surface->firstEdge + i];
		if (e >= 0)
			v = &rg.world->vertices[rg.world->edges[e].v[0]];
		else
			v = &rg.world->vertices[rg.world->edges[-e].v[1]];

		surface->bounds.AddPoint(v->xyz);
	}
}

/*
 ==================
 R_BuildSurfacePolygon

 TODO: set indices for R_CacheGeometry
 ==================
*/
static void R_BuildSurfacePolygon (surface_t *surface){

	triangle_t	*triangle;
	glVertex_t	*vertex, *v;
	texInfo_t	*texInfo = surface->texInfo;
	int			i, e;

	// Create triangle indices
	surface->numTriangles = (surface->numEdges - 2);
	surface->triangles = (triangle_t *)Mem_Alloc(surface->numTriangles * sizeof(triangle_t), TAG_RENDERER);
	rg.world->size += surface->numTriangles * sizeof(triangle_t);

	for (i = 2, triangle = surface->triangles; i < surface->numEdges; i++, triangle++){
		triangle->index[0] = 0;
		triangle->index[1] = i-1;
		triangle->index[2] = i;
	}

	// Create vertices
	surface->numVertices = surface->numEdges;
	surface->vertices = (glVertex_t *)Mem_Alloc(surface->numVertices * sizeof(glVertex_t), TAG_RENDERER);
	rg.world->size += surface->numVertices * sizeof(glVertex_t);

	for (i = 0, vertex = surface->vertices; i < surface->numEdges; i++, vertex++){
		e = rg.world->surfEdges[surface->firstEdge + i];
		if (e >= 0)
			v = &rg.world->vertices[rg.world->edges[e].v[0]];
		else
			v = &rg.world->vertices[rg.world->edges[-e].v[1]];

		vertex->xyz = v->xyz;

		if (!(surface->flags & SURF_PLANEBACK))
			vertex->normal = surface->plane->Normal();
		else
			vertex->normal = -surface->plane->Normal();

		vertex->tangents[0] = texInfo->vecs[0].ToVec3();
		vertex->tangents[1] = texInfo->vecs[1].ToVec3();
		vertex->tangents[0].Normalize();
		vertex->tangents[1].Normalize();

		vertex->st[0] = (v->xyz.Dot(texInfo->vecs[0].ToVec3()) + texInfo->vecs[0][3]) / texInfo->width;
		vertex->st[1] = (v->xyz.Dot(texInfo->vecs[1].ToVec3()) + texInfo->vecs[1][3]) / texInfo->height;

		vertex->color.Set(255);
	}
}

/*
 ==================
 R_FixSurfaceTextureCoords
 ==================
*/
static void R_FixSurfaceTextureCoords (surface_t *surface){

	glVertex_t	*vertex;
	glqVec2		bias;
	float		scale, max;
	int			i;

	if (!surface->texInfo->clamp)
		return;

	// Set bias, scale and maximum value
	bias.Set(M_INFINITY, M_INFINITY);

	scale = 1.0f;
	max = 0.0f;

	// Find the coordinate bias for each axis, which corresponds to the
	// minimum coordinate values
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		if (vertex->st[0] < bias[0])
			bias[0] = vertex->st[0];
		if (vertex->st[1] < bias[1])
			bias[1] = vertex->st[1];
	}

	// Bias so that both axes end up with a minimum coordinate of zero,
	// and find the maximum coordinate value for transforming them to
	// the 0.0 - 1.0 range
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertex->st[0] -= bias[0];
		vertex->st[1] -= bias[1];

		if (vertex->st[0] > max)
			max = vertex->st[0];
		if (vertex->st[1] > max)
			max = vertex->st[1];
	}

	// The scale factor is the inverse of the maximum value
	if (max)
		scale = 1.0f / max;

	// Finally scale so that both axes end up with a maximum coordinate
	// of one
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		vertex->st[0] *= scale;
		vertex->st[1] *= scale;
	}
}

/*
 ==================
 R_LoadFaces
 ==================
*/
static void R_LoadFaces (const byte *data, const bspLump_t *lump){

	bspFace_t	*in;
	surface_t	*out;
	int			i;

	in = (bspFace_t *)(data + lump->offset);
	if (lump->length % sizeof(bspFace_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numSurfaces = lump->length / sizeof(bspFace_t);
	if (rg.world->numSurfaces < 0)
		Com_Error(false, "R_LoadMap: no faces (%s)", rg.world->name);
	else if (rg.world->numSurfaces > BSP_MAX_FACES)
		Com_Error(false, "R_LoadMap: too many faces (%s)", rg.world->name);

	rg.world->surfaces = out = (surface_t *)Mem_Alloc(rg.world->numSurfaces * sizeof(surface_t), TAG_RENDERER);
	rg.world->size += rg.world->numSurfaces * sizeof(surface_t);

	for (i = 0; i < rg.world->numSurfaces; i++, in++, out++){
		out->flags = 0;

		out->firstEdge = LittleLong(in->firstEdge);
		out->numEdges = LittleShort(in->numEdges);

		if (LittleShort(in->side))
			out->flags |= SURF_PLANEBACK;

		out->plane = rg.world->planes + LittleShort(in->planeNum);
		out->texInfo = rg.world->texInfo + LittleShort(in->texInfo);

		// Find bounds
		R_CalcSurfaceBounds(out);

		// Create the polygon
		R_BuildSurfacePolygon(out);

		// Fix texture coordinates for clamping
		R_FixSurfaceTextureCoords(out);

		// Clear index and vertex buffers
		out->indexBuffer = NULL;
		out->indexOffset = 0;

		out->vertexBuffer = NULL;
		out->vertexOffset = 0;

		// Clear counters
		out->viewCount = 0;
		out->checkCount = 0;
	}
}

/*
 ==================
 R_LoadMarkSurfaces
 ==================
*/
static void R_LoadMarkSurfaces (const byte *data, const bspLump_t *lump){

	short		*in;
	surface_t	**out;
	int			i;

	in = (short *)(data + lump->offset);
	if (lump->length % sizeof(short))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numMarkSurfaces = lump->length / sizeof(short);
	if (rg.world->numSurfaces < 0)
		Com_Error(false, "R_LoadMap: no mark surfaces (%s)", rg.world->name);
	else if (rg.world->numSurfaces > BSP_MAX_MARKSURFACES)
		Com_Error(false, "R_LoadMap: too many mark surfaces (%s)", rg.world->name);

	rg.world->markSurfaces = out = (surface_t **)Mem_Alloc(rg.world->numMarkSurfaces * sizeof(surface_t), TAG_RENDERER);
	rg.world->size += rg.world->numMarkSurfaces * sizeof(surface_t);

	for (i = 0; i < rg.world->numMarkSurfaces; i++)
		out[i] = rg.world->surfaces + LittleShort(in[i]);
}

/*
 ==================
 R_LoadLeafs
 ==================
*/
static void R_LoadLeafs (const byte *data, const bspLump_t *lump){

	bspLeaf_t	*in;
	leaf_t		*out;
	int			i, j, p;

	in = (bspLeaf_t *)(data + lump->offset);
	if (lump->length % sizeof(bspLeaf_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numLeafs = lump->length / sizeof(bspLeaf_t);
	if (rg.world->numLeafs < 0)
		Com_Error(false, "R_LoadMap: no leafs (%s)", rg.world->name);
	else if (rg.world->numLeafs > BSP_MAX_LEAFS)
		Com_Error(false, "R_LoadMap: too many leafs (%s)", rg.world->name);

	rg.world->leafs = out = (leaf_t *)Mem_Alloc(rg.world->numLeafs * sizeof(leaf_t), TAG_RENDERER);
	rg.world->size += rg.world->numLeafs * sizeof(leaf_t);

	for (i = 0; i < rg.world->numLeafs; i++, in++, out++){
		out->contents = LittleLong(in->contents);

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleShort(in->mins[j]);
			out->bounds[1][j] = LittleShort(in->maxs[j]);
		}

		out->parent = NULL;

		out->viewCount = 0;
		out->visCount = 0;

		// Leaf specific
		out->firstMarkSurface = rg.world->markSurfaces + LittleShort(in->firstMarkSurface);
		out->numMarkSurfaces = LittleShort(in->numMarkSurfaces);

		p = LittleLong(in->visOfs);
		if (p == -1)
			out->compressedPVS = NULL;
		else
			out->compressedPVS = rg.world->pvs + p;
	}
}

/*
 ==================
 R_SetParent
 ==================
*/
static void R_SetParent (node_t *node, node_t *parent){

	node->parent = parent;

	if (node->contents < 0)
		return;

	R_SetParent(node->children[0], node);
	R_SetParent(node->children[1], node);
}

/*
 ==================
 R_LoadNodes
 ==================
*/
static void R_LoadNodes (const byte *data, const bspLump_t *lump){

	bspNode_t	*in;
	node_t		*out;
	int			i, j, num;

	in = (bspNode_t *)(data + lump->offset);
	if (lump->length % sizeof(bspNode_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numNodes = lump->length / sizeof(bspNode_t);
	if (rg.world->numNodes < 0)
		Com_Error(false, "R_LoadMap: no nodes (%s)", rg.world->name);
	else if (rg.world->numNodes > BSP_MAX_NODES)
		Com_Error(false, "R_LoadMap: too many nodes (%s)", rg.world->name);

	rg.world->nodes = out = (node_t *)Mem_Alloc(rg.world->numNodes * sizeof(node_t), TAG_RENDERER);
	rg.world->size += rg.world->numNodes * sizeof(node_t);

	for (i = 0; i < rg.world->numNodes; i++, in++, out++){
		out->contents = 0;

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleShort(in->mins[j]);
			out->bounds[1][j] = LittleShort(in->maxs[j]);
		}

		out->parent = NULL;

		out->viewCount = 0;
		out->visCount = 0;

		// Node specific
		out->plane = rg.world->planes + LittleLong(in->planeNum);

		for (j = 0; j < 2; j++){
			num = LittleShort(in->children[j]);
			if (num >= 0)
				out->children[j] = rg.world->nodes + num;
			else
				out->children[j] = (node_t *)(rg.world->leafs + (-1 - num));
		}

		out->firstSurface = LittleShort(in->firstFace);
		out->numSurfaces = LittleShort(in->numFaces);
	}

	// Set parents
	R_SetParent(rg.world->nodes, NULL);
}

/*
 ==================
 R_LoadInlineModels
 ==================
*/
static void R_LoadInlineModels (const byte *data, const bspLump_t *lump){

	bspInlineModel_t	*in;
	inlineModel_t		*out;
	int					i, j;

	in = (bspInlineModel_t *)(data + lump->offset);
	if (lump->length % sizeof(bspInlineModel_t))
		Com_Error(false, "R_LoadMap: funny lump size (%s)", rg.world->name);

	rg.world->numInlineModels = lump->length / sizeof(bspInlineModel_t);
	if (rg.world->numInlineModels < 0)
		Com_Error(false, "R_LoadMap: no inline models (%s)", rg.world->name);
	else if (rg.world->numInlineModels > BSP_MAX_INLINEMODELS)
		Com_Error(false, "R_LoadMap: too many inline models (%s)", rg.world->name);

	rg.world->inlineModels = out = (inlineModel_t *)Mem_Alloc(rg.world->numInlineModels * sizeof(inlineModel_t), TAG_RENDERER);
	rg.world->size += rg.world->numInlineModels * sizeof(inlineModel_t);

	for (i = 0; i < rg.world->numInlineModels; i++, in++, out++){
		for (j = 0; j < 3; j++){
			out->origin[j] = LittleFloat(in->origin[j]);

			out->bounds[0][j] = LittleFloat(in->mins[j]) - 1.0f;
			out->bounds[1][j] = LittleFloat(in->maxs[j]) + 1.0f;
		}

		out->numSurfaces = LittleLong(in->numFaces);
		out->surfaces = rg.world->surfaces + LittleLong(in->firstFace);

		// Load the inline model
		R_LoadInlineModel(i, out);
	}
}

/*
 ==================
 
 ==================
*/
static void R_CacheGeometry (){

}

/*
 ==================
 R_LoadMap
 ==================
*/
void R_LoadMap (const char *name){

	bspHeader_t	*header;
	byte		*data;
	int			i;

	// Check if already loaded
	if (rg.world){
		if (!Str_ICompare(rg.world->name, name))
			return;

		Com_Error(false, "R_LoadMap: can't load '%s' because another world map is already loaded", name);
	}

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		Com_Error(false, "R_LoadMap: '%s' not found", name);

	// Allocate the world
	rg.world = (world_t *)Mem_ClearedAlloc(sizeof(world_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(rg.world->name, name, sizeof(rg.world->name));
	rg.world->size = 0;

	// Byte swap the header fields and sanity check
	header = (bspHeader_t *)data;

	for (i = 0; i < sizeof(bspHeader_t) / 4; i++)
		((int *)header)[i] = LittleLong(((int *)header)[i]);

	if (header->version != BSP_VERSION)
		Com_Error(false, "R_LoadMap: wrong version number (%i should be %i) (%s)", header->version, BSP_VERSION, rg.world->name);

	// Load the lumps
	R_LoadPlanes(data, &header->lumps[LUMP_PLANES]);
	R_LoadTextures(data, &header->lumps[LUMP_TEXTURES]);
	R_LoadVertices(data, &header->lumps[LUMP_VERTICES]);
	R_LoadPVS(data, &header->lumps[LUMP_PVS]);
	R_LoadEdges(data, &header->lumps[LUMP_EDGES]);
	R_LoadSurfEdges(data, &header->lumps[LUMP_SURFEDGES]);
	R_LoadTexInfo(data, &header->lumps[LUMP_TEXINFO]);
	R_LoadFaces(data, &header->lumps[LUMP_FACES]);
	R_LoadMarkSurfaces(data, &header->lumps[LUMP_MARKSURFACES]);
	R_LoadLeafs(data, &header->lumps[LUMP_LEAFS]);
	R_LoadNodes(data, &header->lumps[LUMP_NODES]);
	R_LoadInlineModels(data, &header->lumps[LUMP_INLINEMODELS]);

	// Free file data
	FS_FreeFile(data);

	// Clear texture array
	Mem_Fill(r_textureArray, 0, sizeof(r_textureArray));

	// Try to cache the geometry in static index and vertex buffers
	R_CacheGeometry();
}